package com.guli.order.service.impl;
/*
 * @Author 罗俊
 * @date 2020/11/27 - 4:26 下午
 *
 */

import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.domain.ExtendParams;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.request.AlipayTradeCloseRequest;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.alipay.api.response.AlipayTradeCloseResponse;
import com.guli.common.constant.OrderConstant;
import com.guli.common.pojo.vo.StatusCodeEnum;
import com.guli.mall.serviceBase.globalException.MallException;
import com.guli.order.config.AliPayProperties;
import com.guli.order.entity.OrderEntity;
import com.guli.order.entity.PaymentInfoEntity;
import com.guli.order.service.AliPayService;
import com.guli.order.service.OrderItemService;
import com.guli.order.service.OrderService;
import com.guli.order.service.PaymentInfoService;
import com.guli.order.vo.AliPayAsyncCallBackVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;

import static java.util.concurrent.CompletableFuture.supplyAsync;

@Service
@Slf4j
public class AliPayServiceImpl implements AliPayService {

    @Resource
    OrderService orderService;

    @Resource
    OrderItemService orderItemService;

    @Resource
    AliPayProperties aliPayProperties;

    @Resource
    ExecutorService executorService;

    @Resource
    PaymentInfoService paymentInfoService;

    @Override
    public String getOrderInfoAndGoToAliPayment(String orderSn) throws ExecutionException, InterruptedException, AlipayApiException {

        OrderEntity orderEntity = orderService.getOrderByOrderSn(orderSn);

        CompletableFuture<String> queryOrderNameTask = supplyAsync(() -> {
            return orderItemService.getItemsNameByOrderSn(orderSn);
        }, executorService);


        //获得初始化的AlipayClient
        AlipayClient alipayClient = new DefaultAlipayClient(
                aliPayProperties.getGateway(),
                aliPayProperties.getAppId(),
                aliPayProperties.getPrivateKey(),
                "json",
                aliPayProperties.getCharset(),
                aliPayProperties.getAlipayPublicKey(),
                aliPayProperties.getSignType()
        );

        //实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称：alipay.trade.app.pay
        //设置请求参数
        AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
        alipayRequest.setReturnUrl(OrderConstant.ALI_PAY_SUCCESS_RETURN_URL);
        alipayRequest.setNotifyUrl(OrderConstant.ALI_PAY_SUCCESS_NOTIFY_URL);


//SDK已经封装掉了公共参数，这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
        AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();


        BigDecimal payAmount = orderEntity.getPayAmount();

        // 设置金额为2位小数，超出两位小数就向上取整
        BigDecimal scalePayAmount = payAmount.setScale(2, BigDecimal.ROUND_UP);

        // 商户网站唯一订单号
        model.setOutTradeNo(orderSn);

        /**
         * 使用支付宝的自动收单功能
         * 必须要设置定时关单，不然用户停留在支付宝的支付页面30分钟以上，我们已经将超时未支付的订单给取消了，
         * 此时及时支付宝支付成功，也不能修改订单状态了
         * */
        int timeout = OrderConstant.ORDER_PAYMENT_TIME / 1000 / 60;

        model.setTimeoutExpress(timeout + "m");
        model.setTotalAmount(scalePayAmount.toString());

        // 对一笔交易的具体描述信息。如果是多种商品，请将商品描述字符串累加传给body。
        model.setBody("商品描述");

        // 商品标题/交易标题/订单标题/订单关键字等。
        // 注意：不可使用特殊字符，如 /，=，& 等。
        model.setSubject(queryOrderNameTask.get());

        // 销售产品码，商家和支付宝签约的产品码
//        model.setProductCode("QUICK_MSECURITY_PAY"); // 默认的是错误的
        model.setProductCode("FAST_INSTANT_TRADE_PAY");


        /**
         * 方式二设置参数
         */
/*        Map<String, String> map = new HashMap<>();

        map.put("out_trade_no",orderSn);

        map.put("total_amount",scalePayAmount.toString());

        map.put("subject",queryOrderNameTask.get());

        map.put("body","商品描述");

        map.put("product_code","FAST_INSTANT_TRADE_PAY");

        alipayRequest.setBizContent(JSON.toJSONString(map)); */

        alipayRequest.setBizModel(model);

        /**
         * 只有是 pageExecute() 方法才能返回 <form></form> 表单，交由浏览器去执行
         */
        return alipayClient.pageExecute(alipayRequest).getBody();

    }

    @Transactional
    @Override
    public void updatePaymentStatus(AliPayAsyncCallBackVo aliPayAsyncCallBackVo) throws Exception {

        boolean update = orderService.updatePaymentStatus(aliPayAsyncCallBackVo.getOut_trade_no());

        if (!update) {

            log.info("订单 orderSn = {}的订单状态已经不再是{}，说明之前已经被其他服务修改过了（幂等性处理），现不再插入流水数据，直接返回", aliPayAsyncCallBackVo.getOut_trade_no(), OrderConstant.Status.WAITING_FOR_PAYMENT.getMessage());

            return;
        }


        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {

            try {
                paymentInfoService.saveToPaymentInfo(aliPayAsyncCallBackVo);
            } catch (ParseException e) {
                e.printStackTrace();
                throw new MallException(StatusCodeEnum.PARSE_ALIPAY_DATE_STRING_FAILED);
            }

        });
        try {
            Void unused = voidCompletableFuture.get();
        } catch (Exception e) {
            throw e.getCause() instanceof MallException ? (MallException) e.getCause() : e;
        }
    }

    @Override
    public void confirmSign(HttpServletRequest request) throws UnsupportedEncodingException, AlipayApiException {
        /*************************页面功能说明*************************
         * 创建该页面文件时，请留心该页面文件中无任何HTML代码及空格。
         * 该页面不能在本机电脑测试，请到服务器上做测试。请确保外部可以访问该页面。
         * 如果没有收到该页面返回的 success
         * 建议该页面只做支付成功的业务逻辑处理，退款的处理请以调用退款查询接口的结果为准。
         */

        /**
         * 我手动使用了反向解码其编码类型，编码不一致也将导致验证签名失败
         * */
        String charset = request.getParameter("charset");
        //获取支付宝POST过来反馈信息
        Map<String, String> params = new HashMap<String, String>();
        Map<String, String[]> requestParams = request.getParameterMap();
        for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
            String name = (String) iter.next();
            String[] values = (String[]) requestParams.get(name);
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i]
                        : valueStr + values[i] + ",";
            }
            //乱码解决，这段代码在出现乱码时使用
            valueStr = new String(valueStr.getBytes(charset), "utf-8");
            params.put(name, valueStr);
        }

        boolean signVerified = AlipaySignature.rsaCheckV1(params, aliPayProperties.getAlipayPublicKey(), aliPayProperties.getCharset(), aliPayProperties.getSignType()); //调用SDK验证签名

        //——请在这里编写您的程序（以下代码仅作参考）——

	/* 实际验证过程建议商户务必添加以下校验：
	1、需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号，
	2、判断total_amount是否确实为该订单的实际金额（即商户订单创建时的金额），
	3、校验通知中的seller_id（或者seller_email) 是否为out_trade_no这笔单据的对应的操作方（有的时候，一个商户可能有多个seller_id/seller_email）
	4、验证app_id是否为该商户本身。
	*/
        if (signVerified) {//验证成功
            //商户订单号
            String out_trade_no = new String(request.getParameter("out_trade_no").getBytes(charset), "UTF-8");

            //支付宝交易号
            String trade_no = new String(request.getParameter("trade_no").getBytes(charset), "UTF-8");

            log.info("验证签名成功，是支付宝官方发过来的确认消息");

            if (ObjectUtils.isEmpty(request.getParameter("trade_status"))) {
                return;
            }

            //交易状态（同步回调没有这个参数，会报空指针）
            String trade_status = new String(request.getParameter("trade_status").getBytes(charset), "UTF-8");


            if (trade_status.equals("TRADE_FINISHED")) {
                //判断该笔订单是否在商户网站中已经做过处理
                //如果没有做过处理，根据订单号（out_trade_no）在商户网站的订单系统中查到该笔订单的详细，并执行商户的业务程序
                //如果有做过处理，不执行商户的业务程序

                //注意：
                //退款日期超过可退款期限后（如三个月可退款），支付宝系统发送该交易状态通知
            } else if (trade_status.equals("TRADE_SUCCESS")) {
                //判断该笔订单是否在商户网站中已经做过处理
                //如果没有做过处理，根据订单号（out_trade_no）在商户网站的订单系统中查到该笔订单的详细，并执行商户的业务程序
                //如果有做过处理，不执行商户的业务程序

                //注意：
                //付款完成后，支付宝系统发送该交易状态通知
            }


//            out.println("success");

        } else {//验证失败
//            out.println("fail");
            log.error("验证签名失败，可能是：{}", StatusCodeEnum.OTHERS_SEND_A_FAKE_MESSAGE.getMsg());
            throw new MallException(StatusCodeEnum.OTHERS_SEND_A_FAKE_MESSAGE);

            //调试用，写文本函数记录程序运行情况是否正常
            //String sWord = AlipaySignature.getSignCheckContentV1(params);
            //AlipayConfig.logResult(sWord);
        }

        //——请在这里编写您的程序（以上代码仅作参考）——
    }

    @Override
    public void closeAliPay(String trade_no, String out_trade_no, String operator_id) throws AlipayApiException {

        AlipayClient alipayClient = new DefaultAlipayClient(
                aliPayProperties.getGateway(),
                aliPayProperties.getAppId(),
                aliPayProperties.getPrivateKey(),
                "json",
                aliPayProperties.getCharset(),
                aliPayProperties.getAlipayPublicKey(),
                aliPayProperties.getSignType());
        AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();

        Map<String, Object> map = new HashMap<>();

        map.put("trade_no",trade_no);

        map.put("out_trade_no",out_trade_no);

        map.put("operator_id",operator_id);

        request.setBizContent(JSON.toJSONString(map));

        /*request.setBizContent("{" +
                "\"trade_no\":\"2013112611001004680073956707\"," +
                "\"out_trade_no\":\"HZ0120131127001\"," +
                "\"operator_id\":\"YX01\"" +
                "  }");*/
        AlipayTradeCloseResponse response = alipayClient.execute(request);

        if(response.isSuccess()){
//            System.out.println("调用成功");
            log.info("关闭支付宝订单成功，被关闭的订单号是：{}，被关闭的支付宝订单号是：{}，操作者id是：{}",out_trade_no,trade_no,operator_id);
        } else {

            /**
             * https://opendocs.alipay.com/apis/api_1/alipay.trade.close
             *
             * ACQ.SYSTEM_ERROR	系统异常	重新发起请求
             * ACQ.TRADE_NOT_EXIST	交易不存在	检查传入的交易号和外部订单号是否正确，修改后再重新发起
             * ACQ.TRADE_STATUS_ERROR	交易状态不合法	检查当前交易的状态是不是等待买家付款，只有等待买家付款状态下才能发起交易关闭。
             * ACQ.INVALID_PARAMETER	参数无效	检查请求参数，修改后重新发起请求
             * */
            String subCode = response.getSubCode();
//            System.out.println("调用失败");
            log.error("关闭订单失败，关闭失败的订单号是：orderSn = {}，支付宝返回的状态码是：{}，信息：{}，提交给支付宝的参数是：",
                    out_trade_no,response.getSubCode(),response.getSubMsg(),response.getParams().toString());
//            throw new MallException(StatusCodeEnum.CLOSE_ALI_PAY_FAILED.getCode(),"关闭订单失败，关闭失败的订单号是：orderSn = "+out_trade_no);
        }
    }
}
